package com.atguigu.gmall.seckill.service.impl;

import com.atguigu.gmall.common.constans.MqConstant;
import com.atguigu.gmall.common.constans.RedisConstant;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.AuthUtil;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.common.util.JsonUtil;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.model.to.SecKillQueueTo;
import com.atguigu.gmall.model.to.UserAuthTo;
import com.atguigu.gmall.seckill.firstcache.SeckillFirstcache;
import com.atguigu.gmall.seckill.service.SeckillBzService;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author A
 * @version 1.0
 * @description: TODO
 * @date 2025/9/18 19:16
 */
@Slf4j
@Service
public class SeckillBzServiceImpl implements SeckillBzService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private SeckillFirstcache  seckillFirstcache;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    //获取秒杀码
    @Override
    public String getSeckillSkuIdStr(Long skuId) {
        //1.判断是否在秒杀时间内
        String nowDate = DateUtil.formatDate(new Date());
        SeckillGoods goods = seckillFirstcache.getSeckillCache(skuId);
        if (goods.getStartTime().after(new Date())){
            throw new GmallException(ResultCodeEnum.SECKILL_NO_START);
        }
        if (goods.getEndTime().before(new Date())){
            throw new GmallException(ResultCodeEnum.SECKILL_END);
        }
        //2.判断是否还有库存
        if (goods.getStockCount()<=0){
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH);
        }
        UserAuthTo authTo = AuthUtil.getAuthTo();
        Long userId = authTo.getUserId();

        String encrypt = MD5.encrypt(userId + skuId + nowDate);
        //redis中保存秒杀码
        String skuIdStrCodeKey= RedisConstant.SECKILL_SKU_ID_STR_CODE + encrypt;
        redisTemplate.opsForValue().set(skuIdStrCodeKey, "1", 1, TimeUnit.DAYS);

        return encrypt;
    }
    //查询redis中的秒杀订单
    @Override
    public ResultCodeEnum checkOrder(Long skuId) {
        Long userId = AuthUtil.getAuthTo().getUserId();
        String dateStr = DateUtil.formatDate(new Date());
        String key=RedisConstant.SECKILL_ORDER_PREFIX+ userId + ":" + dateStr + ":" + skuId;
        String orderInfoStr = redisTemplate.opsForValue().get(key);
        if (orderInfoStr ==null){
            //说明抢单请求还在mq发送中,返回等待中
            return ResultCodeEnum.SECKILL_RUN;
        }else {
            if ("boom".equals(orderInfoStr)){
                //说明减库存减到负数,秒杀失败
                return ResultCodeEnum.SECKILL_FAIL;
            }else {
                //说明秒杀成功
                OrderInfo orderInfo = JsonUtil.jsonToObject(orderInfoStr, new TypeReference<OrderInfo>() {
                });
                if (orderInfo.getId()==null){
                    //说明订单还未下单,只是抢单成功,返回秒杀成功
                    return ResultCodeEnum.SECKILL_SUCCESS;
                }else {
                    //说明订单已经下单
                    return ResultCodeEnum.SECKILL_ORDER_SUCCESS;
                }
            }
        }
    }
    //秒杀下单,合法性校验,减库存,发送消息
    @Override
    public void seckillOrder(Long skuId, String skuIdStr) {
        Long userId = AuthUtil.getAuthTo().getUserId();
        //1.判断是否在秒杀时间内
        String nowDate = DateUtil.formatDate(new Date());
        SeckillGoods goods = seckillFirstcache.getSeckillCache(skuId);
        if (goods.getStartTime().after(new Date())){
            throw new GmallException(ResultCodeEnum.SECKILL_NO_START);
        }
        if (goods.getEndTime().before(new Date())){
            throw new GmallException(ResultCodeEnum.SECKILL_END);
        }
        //2.判断是否还有库存
        if (goods.getStockCount()<=0){
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH);
        }
        //3.判断秒杀码是否正确
        Boolean checked = checkSeckillCode(skuId, skuIdStr);
        if (!checked){
            throw new GmallException(ResultCodeEnum.SECKILL_ILLEGAL);
        }
        //判断是否重复秒杀
        String codeKey = RedisConstant.SECKILL_SKU_ID_STR_CODE+skuIdStr;
        Long increment = redisTemplate.opsForValue().increment(codeKey);
        if (increment>2){
            //表示第二次以上秒杀,不再发送消息
            log.info(":{}重复秒杀",skuId);
            return;
        }

        //4.先减一级缓存中的库存,因为是concurrentHashMap，所以并发问题不大
        goods.setStockCount(goods.getStockCount()-1);
        if (goods.getStockCount()<0){
            //库存不足
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH);
        }
        //5.能到这里说明一级缓存中库存抢到,减少了大量的并发问题,可以开始减少redis中的信号量
        RSemaphore semaphore = redissonClient.getSemaphore(RedisConstant.SECKILL_GOODS_SEMAPHORE_PREFIX + nowDate + ":" + skuId);
        boolean acquire = semaphore.tryAcquire();//尝试获取信号量,获取成功则减少库存,获取失败则说明秒杀结束
        if (!acquire){
            //获取信号量失败,说明秒杀失败
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH);
        }else {
            //获取信号量成功,说明秒杀成功,发送消息
            SecKillQueueTo secKillQueueTo = new SecKillQueueTo(skuId, userId, skuIdStr, nowDate);
            rabbitTemplate.convertAndSend(MqConstant.SECKILL_ORDER_EXCHANGE, MqConstant.SECKILL_ORDER_ROUTING_KEY, JsonUtil.toStr(secKillQueueTo));
        }


    }
    //判断秒杀码是否正确
    private Boolean checkSeckillCode(Long skuId, String skuIdStr) {
        String str = redisTemplate.opsForValue().get(RedisConstant.SECKILL_SKU_ID_STR_CODE + skuIdStr);
        UserAuthTo authTo = AuthUtil.getAuthTo();
        Long userId = authTo.getUserId();
        String nowDate = DateUtil.formatDate(new Date());
        String encrypt = MD5.encrypt(userId + skuId + nowDate);
        if (!skuIdStr.equals(encrypt)||str==null){
            return false;
        }
        return true;
    }
}
